package com.app.reptile.utils;

import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassUtil {
	private static final String CLASS_SUFFIX = ".class";
	private static final String CLASS_FILE_PREFIX = File.separator + "classes" + File.separator;
	private static final String PACKAGE_SEPARATOR = ".";
//	private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class);

	public static List<Class<?>> getAllClassByInterface(Class<?> clazz) {
		ArrayList<Class<?>> list = new ArrayList<>();
		// 判断是否是一个接口
		if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) {
			try {
				List<Class<?>> allClass = getAllClass(clazz.getPackage().getName());
				/**
				 * 循环判断路径下的所有类是否实现了指定的接口 并且排除接口类自己
				 */
				for (int i = 0; i < allClass.size(); i++) {
					/**
					 * 判断是不是同一个接口
					 */
					// isAssignableFrom:判定此 Class 对象所表示的类或接口与指定的 Class
					// 参数所表示的类或接口是否相同，或是否是其超类或超接口
					if (clazz.isAssignableFrom(allClass.get(i))) {
						if (!clazz.equals(allClass.get(i))) {
							// 自身并不加进去
							list.add(allClass.get(i));
						}
					}
				}
			} catch (Exception e) {
				throw new RuntimeException(e);
//				LOGGER.error("出现异常", e);
			}
		}
//		LOGGER.info("class list size :" + list.size());
		return list;
	}

	/**
	 * 从一个指定路径下查找所有的类
	 *
	 * @param packagename
	 */
	private static List<Class<?>> getAllClass(String packagename) {
//		LOGGER.info("packageName to search：" + packagename);
		List<String> classNameList = getClassName(packagename);
//		LOGGER.info("packageName to search：{}", classNameList);
		List<Class<?>> list = new ArrayList<>();

		for (String className : classNameList) {
			if(!className.startsWith("com.app")){
				continue;
			}
			try {
				list.add(Class.forName(className));
			} catch (ClassNotFoundException e) {
				throw new RuntimeException(e);
//				LOGGER.error("load class from name failed:{}" , className , e);
//				throw new RuntimeException("load class from name failed:" + className + e.getMessage());
			}
		}
//		LOGGER.info("find list size :" + list.size());
		return list;
	}

	/**
	 * 获取某包下所有类
	 * 
	 * @param packageName 包名
	 * @return 类的完整名称
	 */
	public static List<String> getClassName(String packageName) {

		List<String> fileNames = null;
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		String packagePath = packageName.replace(".", "/");
		URL url = loader.getResource(packagePath);
		if (url != null) {
			String type = url.getProtocol();
//			LOGGER.debug("file type : " + type);
			if (type.equals("file")) {
				String fileSearchPath = url.getPath();
//				LOGGER.debug("fileSearchPath: " + fileSearchPath);
				fileSearchPath = fileSearchPath.substring(0, fileSearchPath.indexOf("/classes"));
//				LOGGER.debug("fileSearchPath: " + fileSearchPath);
				fileNames = getClassNameByFile(fileSearchPath);
			} else if (type.equals("jar")) {
				try {
					JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
					JarFile jarFile = jarURLConnection.getJarFile();
					fileNames = getClassNameByJar(jarFile, packagePath);
				} catch (IOException e) {
					throw new RuntimeException("open Package URL failed：" + e.getMessage());
				}

			} else {
				throw new RuntimeException("file system not support! cannot load MsgProcessor！");
			}
		}
		return fileNames;
	}

	/**
	 * 从项目文件获取某包下所有类
	 * 
	 * @param filePath 文件路径
	 * @return 类的完整名称
	 */
	private static List<String> getClassNameByFile(String filePath) {
		List<String> myClassName = new ArrayList<String>();
		File file = new File(filePath);
		File[] childFiles = file.listFiles();
		for (File childFile : childFiles) {
			if (childFile.isDirectory()) {
				myClassName.addAll(getClassNameByFile(childFile.getPath()));
			} else {
				String childFilePath = childFile.getPath();
				if (childFilePath.endsWith(".class")) {
					childFilePath = childFilePath.substring(childFilePath.indexOf("\\classes") + 9,
							childFilePath.lastIndexOf("."));
					childFilePath = childFilePath.replace("\\", ".");
					myClassName.add(childFilePath);
				}
			}
		}

		return myClassName;
	}

	/**
	 * 从jar获取某包下所有类
	 * 
	 * @return 类的完整名称
	 */
	private static List<String> getClassNameByJar(JarFile jarFile, String packagePath) {
		List<String> myClassName = new ArrayList<String>();
		try {
			Enumeration<JarEntry> entrys = jarFile.entries();
			while (entrys.hasMoreElements()) {
				JarEntry jarEntry = entrys.nextElement();
				String entryName = jarEntry.getName();
				if (entryName.endsWith(".class")) {
					entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
					myClassName.add(entryName);
				}
			}
		} catch (Exception e) {
//			LOGGER.error("发生异常:" + e.getMessage());
			throw new RuntimeException(e);
		}
		return myClassName;
	}

	/**
	 * 获取某个包下的所有类
	 */
	public static Set<Class<?>> getClasses(String packageName) throws IOException {
		Set<Class<?>> classSet = new HashSet<>();
		Enumeration<URL> urls = Thread.currentThread().getContextClassLoader()
				.getResources(packageName.replace(".", "/"));
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			if (url != null) {
				String protocol = url.getProtocol();
				if (protocol.equals("file")) {
					String packagePath = url.getPath().replaceAll("%20", " ");
					addClass(classSet, packagePath, packageName);
				} else if (protocol.equals("jar")) {
					JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
					if (jarURLConnection != null) {
						JarFile jarFile = jarURLConnection.getJarFile();
						if (jarFile != null) {
							Enumeration<JarEntry> jarEntries = jarFile.entries();
							while (jarEntries.hasMoreElements()) {
								JarEntry jarEntry = jarEntries.nextElement();
								String jarEntryName = jarEntry.getName();
								if (jarEntryName.endsWith(".class")) {
									String className = jarEntryName.substring(0, jarEntryName.lastIndexOf("."))
											.replaceAll("/", ".");
									doAddClass(classSet, className);
								}
							}
						}
					}
				}
			}
		}
		return classSet;
	}

	private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
		File[] files = new File(packagePath).listFiles(new FileFilter() {
			@Override
			public boolean accept(File file) {
				return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
			}
		});
		for (File file : files) {
			String fileName = file.getName();
			if (file.isFile()) {
				String className = fileName.substring(0, fileName.lastIndexOf("."));
				if (StringUtils.isNotEmpty(packageName)) {
					className = packageName + "." + className;
				}
				doAddClass(classSet, className);
			} else {
				String subPackagePath = fileName;
				if (StringUtils.isNotEmpty(packagePath)) {
					subPackagePath = packagePath + "/" + subPackagePath;
				}
				String subPackageName = fileName;
				if (StringUtils.isNotEmpty(packageName)) {
					subPackageName = packageName + "." + subPackageName;
				}
				addClass(classSet, subPackagePath, subPackageName);
			}
		}
	}

	/**
	 * 加载类
	 */
	public static Class<?> loadClass(String className, boolean isInitialized) {
		Class<?> cls;
		try {
			ClassLoader loader = Thread.currentThread().getContextClassLoader();
			cls = Class.forName(className, isInitialized, loader);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
		return cls;
	}

	/**
	 * 加载类（默认将初始化类）
	 */
	public static Class<?> loadClass(String className) {
		return loadClass(className, true);
	}

	private static void doAddClass(Set<Class<?>> classSet, String className) {
		Class<?> cls = loadClass(className, false);
		classSet.add(cls);
	}

	public static List<String> getClazzName(String packageName, boolean showChildPackageFlag) {
		List<String> result = new ArrayList<>();
		String suffixPath = packageName.replaceAll("\\.", "/");
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		try {
			Enumeration<URL> urls = loader.getResources(suffixPath);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				if (url != null) {
					String protocol = url.getProtocol();
					if ("file".equals(protocol)) {
						String path = url.getPath();
						result.addAll(getAllClassNameByFile(new File(path), showChildPackageFlag));
					} else if ("jar".equals(protocol)) {
						JarFile jarFile = null;
						try {
							jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
						} catch (Exception e) {
							e.printStackTrace();
						}
						if (jarFile != null) {
							result.addAll(getAllClassNameByJar(jarFile, packageName, showChildPackageFlag));
						}
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return result;
	}

	/**
	 * 递归获取所有class文件的名字
	 * 
	 * @param file
	 * @param flag 是否需要迭代遍历
	 * @return List
	 */
	private static List<String> getAllClassNameByFile(File file, boolean flag) {
		List<String> result = new ArrayList<>();
		if (!file.exists()) {
			return result;
		}
		if (file.isFile()) {
			String path = file.getPath();
			// 注意：这里替换文件分割符要用replace。因为replaceAll里面的参数是正则表达式,而windows环境中File.separator="\\"的,因此会有问题
			if (path.endsWith(CLASS_SUFFIX)) {
				path = path.replace(CLASS_SUFFIX, "");
				// 从"/classes/"后面开始截取
				String clazzName = path.substring(path.indexOf(CLASS_FILE_PREFIX) + CLASS_FILE_PREFIX.length())
						.replace(File.separator, PACKAGE_SEPARATOR);
				if (-1 == clazzName.indexOf("$")) {
					result.add(clazzName);
				}
			}
			return result;
		} else {
			File[] listFiles = file.listFiles();
			if (listFiles != null && listFiles.length > 0) {
				for (File f : listFiles) {
					if (flag) {
						result.addAll(getAllClassNameByFile(f, flag));
					} else {
						if (f.isFile()) {
							String path = f.getPath();
							if (path.endsWith(CLASS_SUFFIX)) {
								path = path.replace(CLASS_SUFFIX, "");
								// 从"/classes/"后面开始截取
								String clazzName = path
										.substring(path.indexOf(CLASS_FILE_PREFIX) + CLASS_FILE_PREFIX.length())
										.replace(File.separator, PACKAGE_SEPARATOR);
								if (-1 == clazzName.indexOf("$")) {
									result.add(clazzName);
								}
							}
						}
					}
				}
			}
			return result;
		}
	}

	/**
	 * 递归获取jar所有class文件的名字
	 * 
	 * @param jarFile
	 * @param packageName 包名
	 * @param flag        是否需要迭代遍历
	 * @return List
	 */
	private static List<String> getAllClassNameByJar(JarFile jarFile, String packageName, boolean flag) {
		List<String> result = new ArrayList<>();
		Enumeration<JarEntry> entries = jarFile.entries();
		while (entries.hasMoreElements()) {
			JarEntry jarEntry = entries.nextElement();
			String name = jarEntry.getName();
			// 判断是不是class文件
			if (name.endsWith(CLASS_SUFFIX)) {
				name = name.replace(CLASS_SUFFIX, "").replace("/", ".");
				if (flag) {
					// 如果要子包的文件,那么就只要开头相同且不是内部类就ok
					if (name.startsWith(packageName) && -1 == name.indexOf("$")) {
						result.add(name);
					}
				} else {
					// 如果不要子包的文件,那么就必须保证最后一个"."之前的字符串和包名一样且不是内部类
					if (packageName.equals(name.substring(0, name.lastIndexOf("."))) && -1 == name.indexOf("$")) {
						result.add(name);
					}
				}
			}
		}
		return result;
	}
}
